/*
 * Copyright (c) 2015, Nordic Semiconductor
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
 *
 * 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
 *
 * 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the
 * documentation and/or other materials provided with the distribution.
 *
 * 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this
 * software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
 * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
 * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
 * USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */
package com.dragon.blelibrary.manager;

import android.annotation.TargetApi;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothGatt;
import android.bluetooth.BluetoothGattCallback;
import android.bluetooth.BluetoothGattCharacteristic;
import android.bluetooth.BluetoothGattDescriptor;
import android.bluetooth.BluetoothGattService;
import android.bluetooth.BluetoothManager;
import android.bluetooth.BluetoothProfile;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.os.Build;
import android.os.Handler;
import android.os.Message;
import android.util.Log;

import androidx.annotation.RequiresApi;

import com.dragon.blelibrary.bleutils.BleLOG;
import com.dragon.blelibrary.bleutils.ParserUtils;

import java.lang.reflect.Method;
import java.util.Deque;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.BlockingDeque;
import java.util.concurrent.LinkedBlockingDeque;


/**
 * DO NOT EDIT THIS FILE UNLESS NECESSARY!
 * The BleManager should be overridden in your app and all the 'high level' callbacks should be called from there.
 * Keeping this file as is (and {@link BleManagerCallbacks} as well) will allow to quickly update it when an update is posted here.
 * <p>
 * <p>The BleManager is responsible for managing the low level communication with a Bluetooth Smart device. Please see profiles implementation for an example of use.
 * This base manager has been tested against number of devices and samples from Nordic SDK.</p>
 * <p>The manager handles connection events and initializes the device after establishing the connection.
 * <ol>
 * <li>For bonded devices it ensures that the Service Changed indications, if this characteristic is present, are enabled. Android does not enable them by default,
 * leaving this to the developers.</li>
 * <li>The manager tries to read the Battery Level characteristic. No matter the result of this operation (for example the Battery Level characteristic may not have the READ property)
 * it tries to enable Battery Level notifications, to get battery updates from the device.</li>
 * <li>Afterwards, the manager initializes the device using given queue of commands. See {@link BleManagerGattCallback#initGatt(BluetoothGatt)} method for more details.</li>
 * <li>When initialization complete, the {@link BleManagerCallbacks#onDeviceReady(BluetoothDevice)} callback is called.</li>
 * </ol>The manager also is responsible for parsing the Battery Level values and calling {@link BleManagerCallbacks#onBatteryValueReceived(BluetoothDevice, int)} method.</p>
 * <p>Events from all profiles are being logged into the nRF Logger application,
 * which may be downloaded from Google Play: <a href="https://play.google.com/store/apps/details?id=no.nordicsemi.android.log">https://play.google.com/store/apps/details?id=no.nordicsemi.android.log</a></p>
 * <p>The nRF Logger application allows you to see application logs without need to connect it to the computer.</p>
 *
 * @param <E> The profile callbacks type
 */
abstract class BleManager<E extends BleManagerCallbacks>{
	private final static String TAG = "BleManager";

	protected final static UUID CLIENT_CHARACTERISTIC_CONFIG_DESCRIPTOR_UUID = UUID.fromString("00002902-0000-1000-8000-00805f9b34fb");

	private final static UUID BATTERY_SERVICE = UUID.fromString("0000180F-0000-1000-8000-00805f9b34fb");
	private final static UUID BATTERY_LEVEL_CHARACTERISTIC = UUID.fromString("00002A19-0000-1000-8000-00805f9b34fb");

	private final static UUID GENERIC_ATTRIBUTE_SERVICE = UUID.fromString("00001801-0000-1000-8000-00805f9b34fb");
	private final static UUID SERVICE_CHANGED_CHARACTERISTIC = UUID.fromString("00002A05-0000-1000-8000-00805f9b34fb");

	private final Object mLock = new Object();
	/**
	 * The log session or null if nRF Logger is not installed.
	 */
//	protected ILogSession mLogSession;
	private final Context mContext;
	protected final Handler mHandler;
	protected BluetoothDevice mBluetoothDevice;
	protected E mCallbacks;
	protected BluetoothGatt mBluetoothGatt;
	private BleManagerGattCallback mGattCallback;
	/**
	 * 对于有需要绑定的设备时，在绑定中的状态时，
	 * 是否忽略这个状态而继续去发现服务（主要针对个别需要绑定的设备比较难连接的优化）
	 * true:在绑定中也继续去发现服务
	 * false:等待绑定完成后再去发现服务
	 * 默认（false）
	 */
	private boolean bondIngDiscoverServices;
	/**
	 * This flag is set to false only when the {@link #shouldAutoConnect()} method returns true and the device got disconnected without calling {@link #disconnect()} method.
	 * If {@link #shouldAutoConnect()} returns false (default) this is always set to true.
	 */
	private boolean mUserDisconnected;
	/**
	 * Flag set to true when {@link #shouldAutoConnect()} method returned <code>true</code>. The first connection attempt is done with <code>autoConnect</code>
	 * flag set to false (to make the first connection quick) but on connection lost the manager will call {@link #connect(BluetoothDevice)} again.
	 * This time this method will call {@link BluetoothGatt#connect()} which always uses <code>autoConnect</code> equal true.
	 */
	private boolean mInitialConnection;
	/**
	 * Flag set to true when the device is connected.
	 */
	private boolean mConnected;
	private int mConnectionState = BluetoothGatt.STATE_DISCONNECTED;
	/**
	 * Last received battery value or -1 if value wasn't received.
	 */
	private int mBatteryValue = -1;
	/**
	 * 标记当前正在进行的任务，主要针对发送大数据用,以及读取大数据用
	 */
	private Request mCurrenRequset = null;

	private final BroadcastReceiver mBluetoothStateBroadcastReceiver = new BroadcastReceiver() {
		@Override
		public void onReceive(final Context context, final Intent intent) {

			if (BluetoothDevice.ACTION_ACL_DISCONNECTED.equals(intent.getAction())) {
				BleLOG.d(TAG, "ACL----------蓝牙已断开");
				mHandler.removeMessages(MyHandlerCallback.handler_wait_acl_discontion);
				if(mGattCallback != null)
					mGattCallback.notifyDeviceDisconnected(mBluetoothDevice);
//                BlePropertyObservable.getInstance().fireEvent(BleEventType.ACL_DISCONNECTED, device.getAddress(),message,errorCode,connectParam);
			} else if (BluetoothDevice.ACTION_ACL_DISCONNECT_REQUESTED.equals(intent.getAction())) {
				BleLOG.d(TAG, "ACL---------------蓝牙即将断开");
			}else if (BluetoothDevice.ACTION_ACL_CONNECTED.equals(intent.getAction())) {
				BleLOG.d(TAG, "ACL------------建立了连接");
			}
			if (BluetoothAdapter.ACTION_STATE_CHANGED.equals(intent.getAction())) {
				final int state = intent.getIntExtra(BluetoothAdapter.EXTRA_STATE, BluetoothAdapter.STATE_OFF);
				final int previousState = intent.getIntExtra(BluetoothAdapter.EXTRA_PREVIOUS_STATE, BluetoothAdapter.STATE_OFF);

				final String stateString = "[Broadcast] Action received: " + BluetoothAdapter.ACTION_STATE_CHANGED + ", state changed to " + state2String(state);
				BleLOG.d(TAG, stateString);

				switch (state) {
					case BluetoothAdapter.STATE_TURNING_OFF:
					case BluetoothAdapter.STATE_OFF:
						if (mConnected && previousState != BluetoothAdapter.STATE_TURNING_OFF && previousState != BluetoothAdapter.STATE_OFF) {
							// The connection is killed by the system, no need to gently disconnect
							mGattCallback.notifyDeviceDisconnected(mBluetoothDevice);
						}
						// Calling close() will prevent the STATE_OFF event from being logged (this receiver will be unregistered). But it doesn't matter.
						close();
						break;
				}
			}
		}

		private String state2String(final int state) {
			switch (state) {
				case BluetoothAdapter.STATE_TURNING_ON:
					return "TURNING ON";
				case BluetoothAdapter.STATE_ON:
					return "ON";
				case BluetoothAdapter.STATE_TURNING_OFF:
					return "TURNING OFF";
				case BluetoothAdapter.STATE_OFF:
					return "OFF";
				default:
					return "UNKNOWN (" + state + ")";
			}
		}
	};

	private BroadcastReceiver mBondingBroadcastReceiver = new BroadcastReceiver() {
		@Override
		public void onReceive(final Context context, final Intent intent) {
			final BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
			final int bondState = intent.getIntExtra(BluetoothDevice.EXTRA_BOND_STATE, -1);
			final int previousBondState = intent.getIntExtra(BluetoothDevice.EXTRA_PREVIOUS_BOND_STATE, -1);

			// Skip other devices
			if (mBluetoothGatt == null || !device.getAddress().equals(mBluetoothGatt.getDevice().getAddress()))
				return;

			BleLOG.d(TAG, "[Broadcast] Action received: " + BluetoothDevice.ACTION_BOND_STATE_CHANGED + ", bond state changed to: " + bondStateToString(bondState) + " (" + bondState + ")");
			Log.i(TAG, "Bond state changed for: " + device.getName() + " new state: " + bondState + " previous: " + previousBondState);

			switch (bondState) {
				case BluetoothDevice.BOND_BONDING:
					mCallbacks.onBondingRequired(device);
					break;
				case BluetoothDevice.BOND_BONDED:
					BleLOG.i(TAG, "Device bonded");
					if(bondIngDiscoverServices == false){
						if(mBluetoothGatt != null){//绑定后如果还没发现服务的，重新发现一下
							if(mBluetoothGatt.getServices() == null || mBluetoothGatt.getServices().isEmpty()){
								mBluetoothGatt.discoverServices();
							}
						}
					}
					mCallbacks.onBonded(device);
					break;
			}
		}
	};

	private final BroadcastReceiver mPairingRequestBroadcastReceiver = new BroadcastReceiver() {
		@Override
		public void onReceive(final Context context, final Intent intent) {
			final BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);

			// Skip other devices
			if (mBluetoothGatt == null || !device.getAddress().equals(mBluetoothGatt.getDevice().getAddress()))
				return;

			// String values are used as the constants are not available for Android 4.3.
			final int variant = intent.getIntExtra("android.bluetooth.device.extra.PAIRING_VARIANT"/*BluetoothDevice.EXTRA_PAIRING_VARIANT*/, 0);
			BleLOG.d(TAG, "[Broadcast] Action received: android.bluetooth.device.action.PAIRING_REQUEST"/*BluetoothDevice.ACTION_PAIRING_REQUEST*/ +
					", pairing variant: " + pairingVariantToString(variant) + " (" + variant + ")");

			// The API below is available for Android 4.4 or newer.

			// An app may set the PIN here or set pairing confirmation (depending on the variant) using:
			// device.setPin(new byte[] { '1', '2', '3', '4', '5', '6' });
			// device.setPairingConfirmation(true);
		}
	};

	public BleManager(final Context context) {
		mContext = context;
		mHandler = new Handler(new MyHandlerCallback());
		IntentFilter filter = new IntentFilter();
		filter.addAction(BluetoothAdapter.ACTION_STATE_CHANGED);//指明一个与远程设备建立的低级别（ACL）连接。
		filter.addAction(BluetoothDevice.ACTION_ACL_CONNECTED);//指明一个与远程设备建立的低级别（ACL）连接。
		filter.addAction(BluetoothDevice.ACTION_ACL_DISCONNECTED);//指明一个来自于远程设备的低级别（ACL）连接的断开
		filter.addAction(BluetoothDevice.ACTION_ACL_DISCONNECT_REQUESTED);//指明一个为远程设备提出的低级别（ACL）的断开连接请求，并即将断开连接。
		mContext.registerReceiver(mBluetoothStateBroadcastReceiver, filter);
		BleLOG.d(TAG, "ACL----------注册了广播");
	}

	/**
	 * Returns the context that the manager was created with.
	 *
	 * @return the context
	 */
	protected Context getContext() {
		return mContext;
	}

	/**
	 * This method must return the gatt callback used by the manager.
	 * This method must not create a new gatt callback each time it is being invoked, but rather return a single object.
	 *
	 * @return the gatt callback object
	 */
	protected abstract BleManagerGattCallback getGattCallback();

	/**
	 * Returns whether to connect to the remote device just once (false) or to add the address to white list of devices
	 * that will be automatically connect as soon as they become available (true). In the latter case, if
	 * Bluetooth adapter is enabled, Android scans periodically for devices from the white list and if a advertising packet
	 * is received from such, it tries to connect to it. When the connection is lost, the system will keep trying to reconnect
	 * to it in. If true is returned, and the connection to the device is lost the {@link BleManagerCallbacks#onLinklossOccur(BluetoothDevice)}
	 * callback is called instead of {@link BleManagerCallbacks#onDeviceDisconnected(BluetoothDevice)}.
	 * <p>This feature works much better on newer Android phone models and many not work on older phones.</p>
	 * <p>This method should only be used with bonded devices, as otherwise the device may change it's address.
	 * It will however work also with non-bonded devices with private static address. A connection attempt to
	 * a device with private resolvable address will fail.</p>
	 * <p>The first connection to a device will always be created with autoConnect flag to false
	 * (see {@link BluetoothDevice#connectGatt(Context, boolean, BluetoothGattCallback)}). This is to make it quick as the
	 * user most probably waits for a quick response. However, if this method returned true during first connection and the link was lost,
	 * the manager will try to reconnect to it using {@link BluetoothGatt#connect()} which forces autoConnect to true .</p>
	 *
	 * @return autoConnect flag value
	 */
	protected boolean shouldAutoConnect() {
		return false;
	}

	/**
	 * Connects to the Bluetooth Smart device.
	 *
	 * @param device a device to connect to
	 */
	public void connect(final BluetoothDevice device) {
		if (isConnected() && mGattCallback != null){
			mGattCallback.onConnectionStateChange(mBluetoothGatt, BluetoothGatt.GATT_SUCCESS, BluetoothProfile.STATE_CONNECTED);
			return;
		}

		synchronized (mLock) {
			if (mBluetoothGatt != null) {
				// There are 2 ways of reconnecting to the same device:
				// 1. Reusing the same BluetoothGatt object and calling connect() - this will force the autoConnect flag to true
				// 2. Closing it and reopening a new instance of BluetoothGatt object.
				// The gatt.close() is an asynchronous method. It requires some time before it's finished and
				// device.connectGatt(...) can't be called immediately or service discovery
				// may never finish on some older devices (Nexus 4, Android 5.0.1).
				// If shouldAutoConnect() method returned false we can't call gatt.connect() and have to close gatt and open it again.
				if (!mInitialConnection) {
					BleLOG.d(TAG, "gatt.close()");
					mBluetoothGatt.close();
					mBluetoothGatt = null;
					try {
						BleLOG.d(TAG, "wait(600)");
						Thread.sleep(600); // Is 200 ms enough?
					} catch (final InterruptedException e) {
						// Ignore
					}
				} else {
					// Instead, the gatt.connect() method will be used to reconnect to the same device.
					// This method forces autoConnect = true even if the gatt was created with this flag set to false.
					mInitialConnection = false;
					BleLOG.v(TAG, "Connecting...");
					mConnectionState = BluetoothGatt.STATE_CONNECTING;
					mCallbacks.onDeviceConnecting(device);
					BleLOG.d(TAG, "gatt.connect()");
					mBluetoothGatt.connect();
					return;
				}
			} else {
				// Register bonding broadcast receiver
//				IntentFilter filter = new IntentFilter();
//				filter.addAction(BluetoothAdapter.ACTION_STATE_CHANGED);//指明一个与远程设备建立的低级别（ACL）连接。
//				filter.addAction(BluetoothDevice.ACTION_ACL_CONNECTED);//指明一个与远程设备建立的低级别（ACL）连接。
//				filter.addAction(BluetoothDevice.ACTION_ACL_DISCONNECTED);//指明一个来自于远程设备的低级别（ACL）连接的断开
//				filter.addAction(BluetoothDevice.ACTION_ACL_DISCONNECT_REQUESTED);//指明一个为远程设备提出的低级别（ACL）的断开连接请求，并即将断开连接。
//				mContext.registerReceiver(mBluetoothStateBroadcastReceiver, filter);
				mContext.registerReceiver(mBondingBroadcastReceiver, new IntentFilter(BluetoothDevice.ACTION_BOND_STATE_CHANGED));
				mContext.registerReceiver(mPairingRequestBroadcastReceiver, new IntentFilter("android.bluetooth.device.action.PAIRING_REQUEST"/*BluetoothDevice.ACTION_PAIRING_REQUEST*/));
			}
		}

		final boolean shouldAutoConnect = shouldAutoConnect();
		mUserDisconnected = !shouldAutoConnect; // We will receive Linkloss events only when the device is connected with autoConnect=true
		// The first connection will always be done with autoConnect = false to make the connection quick.
		// If the shouldAutoConnect() method returned true, the manager will automatically try to reconnect to this device on link loss.
		if (shouldAutoConnect)
			mInitialConnection = true;
		mBluetoothDevice = device;
		BleLOG.v(TAG, "Connecting...");
		mConnectionState = BluetoothGatt.STATE_CONNECTING;
		mCallbacks.onDeviceConnecting(device);
		BleLOG.d(TAG, "gatt = device.connectGatt(autoConnect = false)");
//		if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.M){
//            mBluetoothGatt = device.connectGatt(mContext,false, mGattCallback = getGattCallback(), BluetoothDevice.TRANSPORT_LE);
//        }else{
            mBluetoothGatt = device.connectGatt(mContext, false, mGattCallback = getGattCallback());
//        }

	}

	/**
	 * Disconnects from the device or cancels the pending connection attempt. Does nothing if device was not connected.
	 *
	 * @return true if device is to be disconnected. False if it was already disconnected.
	 */
	public boolean disconnect() {
		mUserDisconnected = true;
		mInitialConnection = false;

		if (mBluetoothGatt != null) {
			mConnectionState = BluetoothGatt.STATE_DISCONNECTING;
			BleLOG.v(TAG, mConnected ? "Disconnecting..." : "Cancelling connection...");
			mCallbacks.onDeviceDisconnecting(mBluetoothGatt.getDevice());
			final boolean wasConnected = mConnected;
			BleLOG.d(TAG, "gatt.disconnect()");
			mBluetoothGatt.disconnect();

			if (!wasConnected) {
				// There will be no callback, the connection attempt will be stopped
				mConnectionState = BluetoothGatt.STATE_DISCONNECTED;
				BleLOG.i(TAG, "Disconnected-----------");
				mCallbacks.onDeviceDisconnected(mBluetoothGatt.getDevice());
			}
			return true;
		}
		return false;
	}

	/**
	 * 是否已经发现了服务，
	 * 后续使能通知，发送数据，前提是必须先发现服务，否则，会失败
	 * @return
	 */
	public boolean hasServices(){
		if(mBluetoothGatt != null && mBluetoothGatt.getServices() != null && !mBluetoothGatt.getServices().isEmpty())
			return true;
		return false;
	}
	/**
	 * This method returns true if the device is connected. Services could have not been discovered yet.
	 */
	public boolean isConnected() {
//		BluetoothGatt.GATT_SUCCESS && newState == BluetoothProfile.STATE_CONNECTED
		if(mBluetoothGatt != null
				&& mContext != null
				&& mBluetoothDevice != null
				&& ((BluetoothManager) mContext.getSystemService(Context.BLUETOOTH_SERVICE)).getConnectionState(mBluetoothDevice, BluetoothProfile.GATT) == BluetoothProfile.STATE_CONNECTED
				&& mConnected){
			return true;
		}else{
			return false;
		}
	}

	/**
	 * Method returns the connection state:
	 * {@link BluetoothGatt#STATE_CONNECTING STATE_CONNECTING},
	 * {@link BluetoothGatt#STATE_CONNECTED STATE_CONNECTED},
	 * {@link BluetoothGatt#STATE_DISCONNECTING STATE_DISCONNECTING},
	 * {@link BluetoothGatt#STATE_DISCONNECTED STATE_DISCONNECTED}
	 *
	 * @return the connection state
	 */
	public int getConnectionState() {
		return mConnectionState;
	}

	/**
	 * Returns the last received value of Battery Level characteristic, or -1 if such does not exist, hasn't been read or notification wasn't received yet.
	 *
	 * @return the last battery level value in percent
	 */
	public int getBatteryValue() {
		return mBatteryValue;
	}

	/**
	 * Closes and releases resources. May be also used to unregister broadcast listeners.
	 */
	public void close() {
//		try {
//			mContext.unregisterReceiver(mBluetoothStateBroadcastReceiver);
//			mContext.unregisterReceiver(mBondingBroadcastReceiver);
//			mContext.unregisterReceiver(mPairingRequestBroadcastReceiver);
//		} catch (Exception e) {
//			// the receiver must have been not registered or unregistered before
//		}
		synchronized (mLock) {
			if (mBluetoothGatt != null) {
				BleLOG.d(TAG, "gatt.close()");
				mBluetoothGatt.close();
				mBluetoothGatt = null;
			}
			mConnected = false;
			mInitialConnection = false;
			mConnectionState = BluetoothGatt.STATE_DISCONNECTED;
			mGattCallback = null;
			mBluetoothDevice = null;
		}
	}

	/**
	 * 释放资源
	 */
	public void onDestroy(){
		try {
			mContext.unregisterReceiver(mBluetoothStateBroadcastReceiver);
			mContext.unregisterReceiver(mBondingBroadcastReceiver);
			mContext.unregisterReceiver(mPairingRequestBroadcastReceiver);
			BleLOG.d(TAG, "ACL----------移除了广播");
		} catch (Exception e) {
			// the receiver must have been not registered or unregistered before
		}
	}
//	/**
//	 * Sets the optional log session. This session will be used to log Bluetooth events.
//	 * The logs may be viewed using the nRF Logger application: https://play.google.com/store/apps/details?id=no.nordicsemi.android.log
//	 * Since nRF Logger Library v2.0 an app may define it's own log provider.
//	 * NOTE: nRF Logger must be installed prior to nRF Toolbox as it defines the required permission which is used by nRF Toolbox.
//	 *
//	 * @param session the session, or null if nRF Logger is not installed.
//	 */
//	public void setLogger(final ILogSession session) {
//		mLogSession = session;
//	}
//
//	@Override
//	public void log(final int level, final String message) {
//		Logger.log(mLogSession, level, message);
//	}
//
//	@Override
//	public void log(final int level, @StringRes final int messageRes, final Object... params) {
//		Logger.log(mLogSession, level, messageRes, params);
//	}

	/**
	 * Sets the manager callback listener
	 *
	 * @param callbacks the callback listener
	 */
	public void setGattCallbacks(E callbacks) {
		mCallbacks = callbacks;
	}

	/**
	 * Enqueues creating bond request to the queue.
	 *
	 * @return true if request has been enqueued, false if the device has not been connected
	 */
	protected final boolean createBond() {
		return enqueue(Request.createBond());
	}

	/**
	 * Creates a bond with the device. The device must be first set using {@link #connect(BluetoothDevice)} which will
	 * try to connect to the device. If you need to pair with a device before connecting to it you may do it without
	 * the use of BleManager object and connect after bond is established.
	 *
	 * @return true if pairing has started, false if it was already paired or an immediate error occur.
	 */
	private boolean internalCreateBond() {
		final BluetoothDevice device = mBluetoothDevice;
		if (device == null)
			return false;

		if (device.getBondState() == BluetoothDevice.BOND_BONDED) {
			BleLOG.v(TAG, "Create bond request on already bonded device...");
			BleLOG.i(TAG, "Device bonded");
			return false;
		}

		BleLOG.v(TAG, "Starting pairing...");

		boolean result = false;
		if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
			BleLOG.d(TAG, "device.createBond()");
			result = device.createBond();
		} else {
			/*
			 * There is a createBond() method in BluetoothDevice class but for now it's hidden. We will call it using reflections. It has been revealed in KitKat (Api19)
			 */
			try {
				final Method createBond = device.getClass().getMethod("createBond");
				if (createBond != null) {
					BleLOG.d(TAG, "device.createBond() (hidden)");
					result = (Boolean) createBond.invoke(device);
				}
			} catch (final Exception e) {
				Log.w(TAG, "An com.fenda.myutilslibrary.exception occurred while creating bond", e);
			}
		}

		if (!result)
			Log.w(TAG, "Creating bond failed");
		return result;
	}

	/**
	 * When the device is bonded and has the Generic Attribute service and the Service Changed characteristic this method enables indications on this characteristic.
	 * In case one of the requirements is not fulfilled this method returns <code>false</code>.
	 *
	 * @return <code>true</code> when the request has been sent, <code>false</code> when the device is not bonded, does not have the Generic Attribute service, the GA service does not have
	 * the Service Changed characteristic or this characteristic does not have the CCCD.
	 */
	private boolean ensureServiceChangedEnabled() {
		final BluetoothGatt gatt = mBluetoothGatt;
		if (gatt == null)
			return false;

		// The Service Changed indications have sense only on bonded devices
		final BluetoothDevice device = gatt.getDevice();
		if (device.getBondState() != BluetoothDevice.BOND_BONDED)
			return false;

		final BluetoothGattService gaService = gatt.getService(GENERIC_ATTRIBUTE_SERVICE);
		if (gaService == null)
			return false;

		final BluetoothGattCharacteristic scCharacteristic = gaService.getCharacteristic(SERVICE_CHANGED_CHARACTERISTIC);
		if (scCharacteristic == null)
			return false;

		BleLOG.i(TAG, "Service Changed characteristic found on a bonded device");
		return internalEnableIndications(scCharacteristic);
	}

	/**
	 * Enables notifications on given characteristic.
	 *
	 * @return true is the request has been enqueued
	 */
	protected final boolean enableNotifications(final BluetoothGattCharacteristic characteristic) {
		return enqueue(Request.newEnableNotificationsRequest(characteristic));
	}

	/**
	 * 使能通知
	 * @param characteristic
	 * @return
	 */
	private boolean internalEnableNotifications(final BluetoothGattCharacteristic characteristic) {
		final BluetoothGatt gatt = mBluetoothGatt;
		if (gatt == null || characteristic == null){
			BleLOG.e(TAG,"notifiction gatt = null");
			return false;
		}

		// Check characteristic property
		final int properties = characteristic.getProperties();
		if ((properties & BluetoothGattCharacteristic.PROPERTY_NOTIFY) == 0)
			return false;

		BleLOG.d(TAG, "gatt.setCharacteristicNotification("
				+ GattServiceCharacteristic.findNameByUUid(characteristic.getUuid().toString())
				+ ", true)");
		gatt.setCharacteristicNotification(characteristic, true);
		final BluetoothGattDescriptor descriptor = characteristic.getDescriptor(CLIENT_CHARACTERISTIC_CONFIG_DESCRIPTOR_UUID);
		if (descriptor != null) {
			descriptor.setValue(BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE);
			BleLOG.v(TAG, "Enabling notifications for " + GattServiceCharacteristic.findNameByUUid(characteristic.getUuid().toString()));
			BleLOG.d(TAG, "gatt.writeDescriptor(" + CLIENT_CHARACTERISTIC_CONFIG_DESCRIPTOR_UUID + ", value=0x01-00)");
			return internalWriteDescriptorWorkaround(descriptor);
		}
		return false;
	}

	/**
	 * 反使能通知
	 * @param characteristic
	 * @return
	 */
	private boolean internalDisableNotifications(final BluetoothGattCharacteristic characteristic) {
		final BluetoothGatt gatt = mBluetoothGatt;
		if (gatt == null || characteristic == null)
			return false;

		// Check characteristic property
		final int properties = characteristic.getProperties();
		if ((properties & BluetoothGattCharacteristic.PROPERTY_NOTIFY) == 0)
			return false;

		BleLOG.d(TAG, "gatt.setCharacteristicNotification("
				+ GattServiceCharacteristic.findNameByUUid(characteristic.getUuid().toString())
				+ ", true)");
		gatt.setCharacteristicNotification(characteristic, true);
		final BluetoothGattDescriptor descriptor = characteristic.getDescriptor(CLIENT_CHARACTERISTIC_CONFIG_DESCRIPTOR_UUID);
		if (descriptor != null) {
			descriptor.setValue(BluetoothGattDescriptor.DISABLE_NOTIFICATION_VALUE);
			BleLOG.v(TAG, "Enabling notifications for " + GattServiceCharacteristic.findNameByUUid(characteristic.getUuid().toString()));
			BleLOG.d(TAG, "gatt.writeDescriptor(" + CLIENT_CHARACTERISTIC_CONFIG_DESCRIPTOR_UUID + ", value=0x01-00)");
			return internalWriteDescriptorWorkaround(descriptor);
		}
		return false;
	}
	/**
	 * Enables indications on given characteristic.
	 *
	 * @return true is the request has been enqueued
	 */
	protected final boolean enableIndications(final BluetoothGattCharacteristic characteristic) {
		return enqueue(Request.newEnableIndicationsRequest(characteristic));
	}

	private boolean internalEnableIndications(final BluetoothGattCharacteristic characteristic) {
		final BluetoothGatt gatt = mBluetoothGatt;
		if (gatt == null || characteristic == null)
			return false;

		// Check characteristic property
		final int properties = characteristic.getProperties();
		if ((properties & BluetoothGattCharacteristic.PROPERTY_INDICATE) == 0)
			return false;

		BleLOG.d(TAG, "gatt.setCharacteristicNotification("
				+ GattServiceCharacteristic.findNameByUUid(characteristic.getUuid().toString())
				+ ", true)");
		gatt.setCharacteristicNotification(characteristic, true);
		final BluetoothGattDescriptor descriptor = characteristic.getDescriptor(CLIENT_CHARACTERISTIC_CONFIG_DESCRIPTOR_UUID);
		if (descriptor != null) {
			descriptor.setValue(BluetoothGattDescriptor.ENABLE_INDICATION_VALUE);
			BleLOG.v(TAG, "Enabling indications for " + GattServiceCharacteristic.findNameByUUid(characteristic.getUuid().toString()));
			BleLOG.d(TAG, "gatt.writeDescriptor(" + CLIENT_CHARACTERISTIC_CONFIG_DESCRIPTOR_UUID + ", value=0x02-00)");
			return internalWriteDescriptorWorkaround(descriptor);
		}
		return false;
	}

	/**
	 * Sends the read request to the given characteristic.
	 *
	 * @param characteristic the characteristic to read
	 * @return true if request has been enqueued
	 */
	protected final boolean readCharacteristic(final BluetoothGattCharacteristic characteristic) {
		return enqueue(Request.newReadRequest(characteristic));
	}

	private boolean internalReadCharacteristic(final BluetoothGattCharacteristic characteristic) {
		final BluetoothGatt gatt = mBluetoothGatt;
		if (gatt == null || characteristic == null)
			return false;

		// Check characteristic property
		final int properties = characteristic.getProperties();
		if ((properties & BluetoothGattCharacteristic.PROPERTY_READ) == 0)
			return false;

		BleLOG.v(TAG, "Reading characteristic " + GattServiceCharacteristic.findNameByUUid(characteristic.getUuid().toString()));
//		BleLOG.d(TAG, "gatt.readCharacteristic(" + GattServiceCharacteristic.findNameByUUid(characteristic.getUuid().toString()) + ")");
		return gatt.readCharacteristic(characteristic);
	}

	/**
	 * Writes the characteristic value to the given characteristic.
	 *
	 * @param characteristic the characteristic to write to
	 * @return true if request has been enqueued
	 */
	protected final boolean writeCharacteristic(final BluetoothGattCharacteristic characteristic) {
		return enqueue(Request.newWriteRequest(characteristic, characteristic.getValue()));
	}

	private boolean internalWriteCharacteristic(final BluetoothGattCharacteristic characteristic) {
		final BluetoothGatt gatt = mBluetoothGatt;
		if (gatt == null || characteristic == null)
			return false;

		// Check characteristic property
		final int properties = characteristic.getProperties();
		if ((properties & (BluetoothGattCharacteristic.PROPERTY_WRITE | BluetoothGattCharacteristic.PROPERTY_WRITE_NO_RESPONSE)) == 0)
			return false;

		BleLOG.v(TAG, "Writing characteristic "
				+ GattServiceCharacteristic.findNameByUUid(characteristic.getUuid().toString())
				+ " (" + getWriteType(characteristic.getWriteType()) + ")");
//		BleLOG.d(TAG, "gatt.writeCharacteristic(" + GattServiceCharacteristic.findNameByUUid(characteristic.getUuid().toString()) + ")");
		return gatt.writeCharacteristic(characteristic);
	}

	/**
	 * Sends the read request to the given descriptor.
	 *
	 * @param descriptor the descriptor to read
	 * @return true if request has been enqueued
	 */
	protected final boolean readDescriptor(final BluetoothGattDescriptor descriptor) {
		return enqueue(Request.newReadRequest(descriptor));
	}

	private boolean internalReadDescriptor(final BluetoothGattDescriptor descriptor) {
		final BluetoothGatt gatt = mBluetoothGatt;
		if (gatt == null || descriptor == null)
			return false;

		BleLOG.v(TAG, "Reading descriptor " + GattServiceCharacteristic.findNameByUUid(descriptor.getUuid().toString()));
//		BleLOG.d(TAG, "gatt.readDescriptor(" + GattServiceCharacteristic.findNameByUUid(descriptor.getUuid().toString()) + ")");
		return gatt.readDescriptor(descriptor);
	}

	/**
	 * Writes the descriptor value to the given descriptor.
	 *
	 * @param descriptor the descriptor to write to
	 * @return true if request has been enqueued
	 */
	protected final boolean writeDescriptor(final BluetoothGattDescriptor descriptor) {
		return enqueue(Request.newWriteRequest(descriptor, descriptor.getValue()));
	}

	private boolean internalWriteDescriptor(final BluetoothGattDescriptor descriptor) {
		final BluetoothGatt gatt = mBluetoothGatt;
		if (gatt == null || descriptor == null)
			return false;

		BleLOG.v(TAG, "Writing descriptor " + GattServiceCharacteristic.findNameByUUid(descriptor.getUuid().toString()));
//		BleLOG.d(TAG, "gatt.writeDescriptor(" + GattServiceCharacteristic.findNameByUUid(descriptor.getUuid().toString()) + ")");
		return internalWriteDescriptorWorkaround(descriptor);
	}

	/**
	 * Reads the battery level from the device.
	 *
	 * @return true if request has been enqueued
	 */
	public final boolean readBatteryLevel() {
		return enqueue(Request.newReadBatteryLevelRequest());
	}

	private boolean internalReadBatteryLevel() {
		final BluetoothGatt gatt = mBluetoothGatt;
		if (gatt == null)
			return false;

		final BluetoothGattService batteryService = gatt.getService(BATTERY_SERVICE);
		if (batteryService == null)
			return false;

		final BluetoothGattCharacteristic batteryLevelCharacteristic = batteryService.getCharacteristic(BATTERY_LEVEL_CHARACTERISTIC);
		if (batteryLevelCharacteristic == null)
			return false;

		// Check characteristic property
		final int properties = batteryLevelCharacteristic.getProperties();
		if ((properties & BluetoothGattCharacteristic.PROPERTY_READ) == 0)
			return false;

		BleLOG.a(TAG, "Reading battery level...");
		return internalReadCharacteristic(batteryLevelCharacteristic);
	}

	/**
	 * This method tries to enable notifications on the Battery Level characteristic.
	 *
	 * @param enable <code>true</code> to enable battery notifications, false to disable
	 * @return true if request has been enqueued
	 */
	public final boolean setBatteryNotifications(final boolean enable) {
		if (enable)
			return enqueue(Request.newEnableBatteryLevelNotificationsRequest());
		else
			return enqueue(Request.newDisableBatteryLevelNotificationsRequest());
	}

	private boolean internalSetBatteryNotifications(final boolean enable) {
		final BluetoothGatt gatt = mBluetoothGatt;
		if (gatt == null) {
			return false;
		}

		final BluetoothGattService batteryService = gatt.getService(BATTERY_SERVICE);
		if (batteryService == null)
			return false;

		final BluetoothGattCharacteristic batteryLevelCharacteristic = batteryService.getCharacteristic(BATTERY_LEVEL_CHARACTERISTIC);
		if (batteryLevelCharacteristic == null)
			return false;

		// Check characteristic property
		final int properties = batteryLevelCharacteristic.getProperties();
		if ((properties & BluetoothGattCharacteristic.PROPERTY_NOTIFY) == 0)
			return false;

		gatt.setCharacteristicNotification(batteryLevelCharacteristic, enable);
		final BluetoothGattDescriptor descriptor = batteryLevelCharacteristic.getDescriptor(CLIENT_CHARACTERISTIC_CONFIG_DESCRIPTOR_UUID);
		if (descriptor != null) {
			if (enable) {
				descriptor.setValue(BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE);
				BleLOG.a(TAG, "Enabling battery level notifications...");
				BleLOG.v(TAG, "Enabling notifications for " + BATTERY_LEVEL_CHARACTERISTIC);
				BleLOG.d(TAG, "gatt.writeDescriptor(" + CLIENT_CHARACTERISTIC_CONFIG_DESCRIPTOR_UUID + ", value=0x0100)");
			} else {
				descriptor.setValue(BluetoothGattDescriptor.DISABLE_NOTIFICATION_VALUE);
				BleLOG.a(TAG, "Disabling battery level notifications...");
				BleLOG.v(TAG, "Disabling notifications for " + BATTERY_LEVEL_CHARACTERISTIC);
				BleLOG.d(TAG, "gatt.writeDescriptor(" + CLIENT_CHARACTERISTIC_CONFIG_DESCRIPTOR_UUID + ", value=0x0000)");
			}
			return internalWriteDescriptorWorkaround(descriptor);
		}
		return false;
	}

	/**
	 * There was a bug in Android up to 6.0 where the descriptor was written using parent
	 * characteristic's write type, instead of always Write With Response, as the spec says.
	 * <p>
	 * See: <a href="https://android.googlesource.com/platform/frameworks/base/+/942aebc95924ab1e7ea1e92aaf4e7fc45f695a6c%5E%21/#F0">
	 * https://android.googlesource.com/platform/frameworks/base/+/942aebc95924ab1e7ea1e92aaf4e7fc45f695a6c%5E%21/#F0</a>
	 * </p>
	 *
	 * @param descriptor the descriptor to be written
	 * @return the result of {@link BluetoothGatt#writeDescriptor(BluetoothGattDescriptor)}
	 */
	private boolean internalWriteDescriptorWorkaround(final BluetoothGattDescriptor descriptor) {
		final BluetoothGatt gatt = mBluetoothGatt;
		if (gatt == null || descriptor == null)
			return false;

		final BluetoothGattCharacteristic parentCharacteristic = descriptor.getCharacteristic();
		final int originalWriteType = parentCharacteristic.getWriteType();
		parentCharacteristic.setWriteType(BluetoothGattCharacteristic.WRITE_TYPE_DEFAULT);
		final boolean result = gatt.writeDescriptor(descriptor);
		parentCharacteristic.setWriteType(originalWriteType);
		return result;
	}

	/**
	 * Requests new MTU. On Android 4.3 and 4.4.x returns false.
	 *
	 * @return true if request has been enqueued
	 */
	public final boolean requestMtu(final int mtu) {
		return Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP && enqueue(Request.newMtuRequest(mtu));
	}

	@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
	private boolean internalRequestMtu(final int mtu) {
		final BluetoothGatt gatt = mBluetoothGatt;
		if (gatt == null)
			return false;

		BleLOG.v(TAG, "Requesting new MTU...");
		BleLOG.d(TAG, "gatt.requestMtu(" + mtu + ")");
		return gatt.requestMtu(mtu);
	}

	/**
	 * Requests the new connection priority. Acceptable values are:
	 * <ol>
	 * <li>{@link BluetoothGatt#CONNECTION_PRIORITY_HIGH} - Interval: 11.25 -15 ms, latency: 0, supervision timeout: 20 sec,</li>
	 * <li>{@link BluetoothGatt#CONNECTION_PRIORITY_BALANCED} - Interval: 30 - 50 ms, latency: 0, supervision timeout: 20 sec,</li>
	 * <li>{@link BluetoothGatt#CONNECTION_PRIORITY_LOW_POWER} - Interval: 100 - 125 ms, latency: 2, supervision timeout: 20 sec.</li>
	 * </ol>
	 * On Android 4.3 and 4.4.x returns false.
	 *
	 * @param priority one of: {@link BluetoothGatt#CONNECTION_PRIORITY_HIGH}, {@link BluetoothGatt#CONNECTION_PRIORITY_BALANCED},
	 *                 {@link BluetoothGatt#CONNECTION_PRIORITY_LOW_POWER}.
	 * @return true if request has been enqueued
	 */
	public final boolean requestConnectionPriority(final int priority) {
		return Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP && enqueue(Request.newConnectionPriorityRequest(priority));
	}

	@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
	private boolean internalRequestConnectionPriority(final int priority) {
		final BluetoothGatt gatt = mBluetoothGatt;
		if (gatt == null)
			return false;

		String text, priorityText;
		switch (priority) {
			case BluetoothGatt.CONNECTION_PRIORITY_HIGH:
				text = Build.VERSION.SDK_INT >= Build.VERSION_CODES.M ? "HIGH (11.25–15ms, 0, 20s)" : "HIGH (7.5–10ms, 0, 20s)";
				priorityText = "HIGH";
				break;
			case BluetoothGatt.CONNECTION_PRIORITY_LOW_POWER:
				text = "BALANCED (30–50ms, 0, 20s)";
				priorityText = "LOW POWER";
				break;
			default:
			case BluetoothGatt.CONNECTION_PRIORITY_BALANCED:
				text = "LOW POWER (100–125ms, 2, 20s)";
				priorityText = "BALANCED";
				break;
		}
		BleLOG.v(TAG, "Requesting connection priority: " + text + "...");
		BleLOG.d(TAG, "gatt.requestConnectionPriority(" + priorityText + ")");
		return gatt.requestConnectionPriority(priority);
	}

	/**
	 * 发送数据
	 * @param request
	 * @return
	 */
	public boolean sendMsg(Request request){
		if(mCurrenRequset == null){
			mCurrenRequset = request;
			if(request.type == Request.Type.READ){
				return internalReadCharacteristic(request.characteristic);
			}else if(request.type == Request.Type.WRITE){
				final BluetoothGattCharacteristic characteristic = request.characteristic;
				characteristic.setValue(request.data);
				characteristic.setWriteType(request.writeType);
				return internalWriteCharacteristic(characteristic);
			}
			return false;
		}else{
			try {
				mGattCallback.mTaskQueue.addLast(request);
				return true;
			} catch (Exception e) {
				e.printStackTrace();
				return false;
			}
		}
	}

	/**
	 * 使能其他未使能的通知
	 * @param list
	 * @return
	 */
	protected boolean enAllNotify(Deque<Request> list){
		if(mGattCallback != null){
			if(mGattCallback.mInitQueue == null){
				mGattCallback.mInitQueue = new LinkedList<>();
			}
			mGattCallback.mInitQueue.addAll(list);
			mGattCallback.mInitInProgress = true;
			mGattCallback.nextRequest(true);
			return true;
		}
		return false;
	}

	/**
	 * Enqueues a new request. The request will be handled immediately if there is no operation in progress,
	 * or automatically after the last enqueued one will finish.
	 * <p>This method should be used to read and write data from the target device as it ensures that the last operation has finished
	 * before a new one will be called.</p>
	 *
	 * @param request new request to be added to the queue.
	 * @return true if request has been enqueued, false if the {@link #connect(BluetoothDevice)} method was not called before,
	 * or the manager was closed using {@link #close()}.
	 */

	public boolean enqueue(final Request request) {

		if (mGattCallback != null) {
			// Add the new task to the end of the queue
			try {
				mGattCallback.mTaskQueue.addLast(request);
			} catch (Exception e) {
				e.printStackTrace();
			}
			mGattCallback.nextRequest(true);

			return true;
		}
		return false;
	}

	/**
	 * 读取设备的信号强度
	 * @return
	 */
	public boolean readRemoteRssi(){
		final BluetoothGatt gatt = mBluetoothGatt;
		if (gatt == null)
			return false;
		return gatt.readRemoteRssi();
	}
	/**
	 * On Android, when multiple BLE operations needs to be done, it is required to wait for a proper
	 * {@link BluetoothGattCallback BluetoothGattCallback} callback before calling
	 * another operation. In order to make BLE operations easier the BleManager allows to enqueue a request
	 * containing all data necessary for a given operation. Requests are performed one after another until the
	 * queue is empty. Use static methods from below to instantiate a request and then enqueue them using {@link #enqueue(Request)}.
	 */
	protected static final class Request {
		private enum Type {
			CREATE_BOND,
			WRITE,
			READ,
			WRITE_DESCRIPTOR,
			READ_DESCRIPTOR,
			/**使能通知*/
			ENABLE_NOTIFICATIONS,
			/**反使能通知*/
			DISABLE_NOTIFICATIONS,
			ENABLE_INDICATIONS,
			READ_BATTERY_LEVEL,
			ENABLE_BATTERY_LEVEL_NOTIFICATIONS,
			DISABLE_BATTERY_LEVEL_NOTIFICATIONS,
			ENABLE_SERVICE_CHANGED_INDICATIONS,
			REQUEST_MTU,
			REQUEST_CONNECTION_PRIORITY,
		}

		private final Type type;
		private final BluetoothGattCharacteristic characteristic;
		private final BluetoothGattDescriptor descriptor;
		private final byte[] data;
		private final int writeType;
		private final int value;

		private Request(final Type type) {
			this.type = type;
			this.characteristic = null;
			this.descriptor = null;
			this.data = null;
			this.writeType = 0;
			this.value = 0;
		}

		private Request(final Type type, final int value) {
			this.type = type;
			this.characteristic = null;
			this.descriptor = null;
			this.data = null;
			this.writeType = 0;
			this.value = value;
		}

		private Request(final Type type, final BluetoothGattCharacteristic characteristic) {
			this.type = type;
			this.characteristic = characteristic;
			this.descriptor = null;
			this.data = null;
			this.writeType = 0;
			this.value = 0;
		}

		private Request(final Type type, final BluetoothGattCharacteristic characteristic, final int writeType, final byte[] data, final int offset, final int length) {
			this.type = type;
			this.characteristic = characteristic;
			this.descriptor = null;
			this.data = copy(data, offset, length);
			this.writeType = writeType;
			this.value = 0;
		}

		private Request(final Type type, final BluetoothGattDescriptor descriptor) {
			this.type = type;
			this.characteristic = null;
			this.descriptor = descriptor;
			this.data = null;
			this.writeType = 0;
			this.value = 0;
		}

		private Request(final Type type, final BluetoothGattDescriptor descriptor, final byte[] data, final int offset, final int length) {
			this.type = type;
			this.characteristic = null;
			this.descriptor = descriptor;
			this.data = copy(data, offset, length);
			this.writeType = BluetoothGattCharacteristic.WRITE_TYPE_DEFAULT;
			this.value = 0;
		}

		private static byte[] copy(final byte[] value, final int offset, final int length) {
			if (value == null || offset > value.length)
				return null;
			final int maxLength = Math.min(value.length - offset, length);
			final byte[] copy = new byte[maxLength];
			System.arraycopy(value, offset, copy, 0, maxLength);
			return copy;
		}

		/**
		 * Creates a new request that will start pairing with the device.
		 *
		 * @return the new request that can be enqueued using {@link #enqueue(Request)} method.
		 */
		public static Request createBond() {
			return new Request(Type.CREATE_BOND);
		}

		/**
		 * Creates new Read Characteristic request. The request will not be executed if given characteristic
		 * is null or does not have READ property. After the operation is complete a proper callback will be invoked.
		 *
		 * @param characteristic characteristic to be read
		 * @return the new request that can be enqueued using {@link #enqueue(Request)} method.
		 */
		public static Request newReadRequest(final BluetoothGattCharacteristic characteristic) {
			return new Request(Type.READ, characteristic);
		}

		/**
		 * Creates new Write Characteristic request. The request will not be executed if given characteristic
		 * is null or does not have WRITE property. After the operation is complete a proper callback will be invoked.
		 *
		 * @param characteristic characteristic to be written
		 * @param value          value to be written. The array is copied into another buffer so it's safe to reuse the array again.
		 * @return the new request that can be enqueued using {@link #enqueue(Request)} method.
		 */
		public static Request newWriteRequest(final BluetoothGattCharacteristic characteristic, final byte[] value) {
			return new Request(Type.WRITE, characteristic, characteristic.getWriteType(), value, 0, value != null ? value.length : 0);
		}

		/**
		 * Creates new Write Characteristic request. The request will not be executed if given characteristic
		 * is null or does not have WRITE property. After the operation is complete a proper callback will be invoked.
		 *
		 * @param characteristic characteristic to be written
		 * @param value          value to be written. The array is copied into another buffer so it's safe to reuse the array again.
		 * @param writeType      write type to be used, one of {@link BluetoothGattCharacteristic#WRITE_TYPE_DEFAULT}, {@link BluetoothGattCharacteristic#WRITE_TYPE_NO_RESPONSE}.
		 * @return the new request that can be enqueued using {@link #enqueue(Request)} method.
		 */
		public static Request newWriteRequest(final BluetoothGattCharacteristic characteristic, final byte[] value, final int writeType) {
			return new Request(Type.WRITE, characteristic, writeType, value, 0, value != null ? value.length : 0);
		}

		/**
		 * Creates new Write Characteristic request. The request will not be executed if given characteristic
		 * is null or does not have WRITE property. After the operation is complete a proper callback will be invoked.
		 *
		 * @param characteristic characteristic to be written
		 * @param value          value to be written. The array is copied into another buffer so it's safe to reuse the array again.
		 * @param offset         the offset from which value has to be copied
		 * @param length         number of bytes to be copied from the value buffer
		 * @return the new request that can be enqueued using {@link #enqueue(Request)} method.
		 */
		public static Request newWriteRequest(final BluetoothGattCharacteristic characteristic, final byte[] value, final int offset, final int length) {
			return new Request(Type.WRITE, characteristic, characteristic.getWriteType(), value, offset, length);
		}

		/**
		 * Creates new Write Characteristic request. The request will not be executed if given characteristic
		 * is null or does not have WRITE property. After the operation is complete a proper callback will be invoked.
		 *
		 * @param characteristic characteristic to be written
		 * @param value          value to be written. The array is copied into another buffer so it's safe to reuse the array again.
		 * @param offset         the offset from which value has to be copied
		 * @param length         number of bytes to be copied from the value buffer
		 * @param writeType      write type to be used, one of {@link BluetoothGattCharacteristic#WRITE_TYPE_DEFAULT}, {@link BluetoothGattCharacteristic#WRITE_TYPE_NO_RESPONSE}.
		 * @return the new request that can be enqueued using {@link #enqueue(Request)} method.
		 */
		public static Request newWriteRequest(final BluetoothGattCharacteristic characteristic, final byte[] value, final int offset, final int length, final int writeType) {
			return new Request(Type.WRITE, characteristic, writeType, value, offset, length);
		}

		/**
		 * Creates new Read Descriptor request. The request will not be executed if given descriptor
		 * is null. After the operation is complete a proper callback will be invoked.
		 *
		 * @param descriptor descriptor to be read
		 * @return the new request that can be enqueued using {@link #enqueue(Request)} method.
		 */
		public static Request newReadRequest(final BluetoothGattDescriptor descriptor) {
			return new Request(Type.READ_DESCRIPTOR, descriptor);
		}

		/**
		 * Creates new Write Descriptor request. The request will not be executed if given descriptor
		 * is null. After the operation is complete a proper callback will be invoked.
		 *
		 * @param descriptor descriptor to be written
		 * @param value      value to be written. The array is copied into another buffer so it's safe to reuse the array again.
		 * @return the new request that can be enqueued using {@link #enqueue(Request)} method.
		 */
		public static Request newWriteRequest(final BluetoothGattDescriptor descriptor, final byte[] value) {
			return new Request(Type.WRITE_DESCRIPTOR, descriptor, value, 0, value != null ? value.length : 0);
		}

		/**
		 * Creates new Write Descriptor request. The request will not be executed if given descriptor
		 * is null. After the operation is complete a proper callback will be invoked.
		 *
		 * @param descriptor descriptor to be written
		 * @param value      value to be written. The array is copied into another buffer so it's safe to reuse the array again.
		 * @param offset     the offset from which value has to be copied
		 * @param length     number of bytes to be copied from the value buffer
		 * @return the new request that can be enqueued using {@link #enqueue(Request)} method.
		 */
		public static Request newWriteRequest(final BluetoothGattDescriptor descriptor, final byte[] value, final int offset, final int length) {
			return new Request(Type.WRITE_DESCRIPTOR, descriptor, value, offset, length);
		}

		/**
		 * Creates new Enable Notification request. The request will not be executed if given characteristic
		 * is null, does not have NOTIFY property or the CCCD. After the operation is complete a proper callback will be invoked.
		 *
		 * @param characteristic characteristic to have notifications enabled
		 * @return the new request that can be enqueued using {@link #enqueue(Request)} method.
		 */
		public static Request newEnableNotificationsRequest(final BluetoothGattCharacteristic characteristic) {
			return new Request(Type.ENABLE_NOTIFICATIONS, characteristic);
		}

		/**
		 * 反使能通知
		 * @param characteristic
		 * @return
		 */
		public static Request newDisableNotificationRequest(final BluetoothGattCharacteristic characteristic){
			return new Request(Type.DISABLE_NOTIFICATIONS, characteristic);
		}
		/**
		 * Creates new Enable Indications request. The request will not be executed if given characteristic
		 * is null, does not have INDICATE property or the CCCD. After the operation is complete a proper callback will be invoked.
		 *
		 * @param characteristic characteristic to have indications enabled
		 * @return the new request that can be enqueued using {@link #enqueue(Request)} method.
		 */
		public static Request newEnableIndicationsRequest(final BluetoothGattCharacteristic characteristic) {
			return new Request(Type.ENABLE_INDICATIONS, characteristic);
		}

		/**
		 * Reads the first found Battery Level characteristic value from the first found Battery Service.
		 * If any of them is not found, or the characteristic does not have the READ property this operation will not execute.
		 *
		 * @return the new request that can be enqueued using {@link #enqueue(Request)} method.
		 */
		public static Request newReadBatteryLevelRequest() {
			return new Request(Type.READ_BATTERY_LEVEL); // the first Battery Level char from the first Battery Service is used
		}

		/**
		 * Enables notifications on the first found Battery Level characteristic from the first found Battery Service.
		 * If any of them is not found, or the characteristic does not have the NOTIFY property this operation will not execute.
		 *
		 * @return the new request that can be enqueued using {@link #enqueue(Request)} method.
		 */
		public static Request newEnableBatteryLevelNotificationsRequest() {
			return new Request(Type.ENABLE_BATTERY_LEVEL_NOTIFICATIONS); // the first Battery Level char from the first Battery Service is used
		}

		/**
		 * Disables notifications on the first found Battery Level characteristic from the first found Battery Service.
		 * If any of them is not found, or the characteristic does not have the NOTIFY property this operation will not execute.
		 *
		 * @return the new request that can be enqueued using {@link #enqueue(Request)} method.
		 */
		public static Request newDisableBatteryLevelNotificationsRequest() {
			return new Request(Type.DISABLE_BATTERY_LEVEL_NOTIFICATIONS); // the first Battery Level char from the first Battery Service is used
		}

		/**
		 * Enables indications on Service Changed characteristic if such exists in the Generic Attribute service.
		 * It is required to enable those notifications on bonded devices on older Android versions to be
		 * informed about attributes changes. Android 7+ (or 6+) handles this automatically and no action is required.
		 *
		 * @return the new request that can be enqueued using {@link #enqueue(Request)} method.
		 */
		private static Request newEnableServiceChangedIndicationsRequest() {
			return new Request(Type.ENABLE_SERVICE_CHANGED_INDICATIONS); // the only Service Changed char is used (if such exists)
		}

		/**
		 * Requests new MTU (Maximum Transfer Unit). This is only supported on Android Lollipop or newer.
		 * The target device may reject requested value and set smalled MTU.
		 *
		 * @param mtu the new MTU. Acceptable values are &lt;23, 517&gt;.
		 * @return the new request that can be enqueued using {@link #enqueue(Request)} method.
		 */
		public static Request newMtuRequest(int mtu) {
			if (mtu < 23)
				mtu = 23;
			if (mtu > 517)
				mtu = 517;
			return new Request(Type.REQUEST_MTU, mtu);
		}

		/**
		 * Requests the new connection priority. Acceptable values are:
		 * <ol>
		 * <li>{@link BluetoothGatt#CONNECTION_PRIORITY_HIGH} - Interval: 11.25 -15 ms, latency: 0, supervision timeout: 20 sec,</li>
		 * <li>{@link BluetoothGatt#CONNECTION_PRIORITY_BALANCED} - Interval: 30 - 50 ms, latency: 0, supervision timeout: 20 sec,</li>
		 * <li>{@link BluetoothGatt#CONNECTION_PRIORITY_LOW_POWER} - Interval: 100 - 125 ms, latency: 2, supervision timeout: 20 sec.</li>
		 * </ol>
		 *
		 * @param priority one of: {@link BluetoothGatt#CONNECTION_PRIORITY_HIGH}, {@link BluetoothGatt#CONNECTION_PRIORITY_BALANCED},
		 *                 {@link BluetoothGatt#CONNECTION_PRIORITY_LOW_POWER}.
		 * @return the new request that can be enqueued using {@link #enqueue(Request)} method.
		 */
		public static Request newConnectionPriorityRequest(int priority) {
			if (priority < 0 || priority > 2)
				priority = 0; // Balanced
			return new Request(Type.REQUEST_CONNECTION_PRIORITY, priority);
		}
	}

	protected abstract class BleManagerGattCallback extends BluetoothGattCallback {
		private final static String ERROR_CONNECTION_STATE_CHANGE = "Error on connection state change";
		private final static String ERROR_DISCOVERY_SERVICE = "Error on discovering services";
		private final static String ERROR_AUTH_ERROR_WHILE_BONDED = "Phone has lost bonding information";
		private final static String ERROR_READ_CHARACTERISTIC = "Error on reading characteristic";
		private final static String ERROR_WRITE_CHARACTERISTIC = "Error on writing characteristic";
		private final static String ERROR_READ_DESCRIPTOR = "Error on reading descriptor";
		private final static String ERROR_WRITE_DESCRIPTOR = "Error on writing descriptor";
		private final static String ERROR_MTU_REQUEST = "Error on mtu request";
		private final static String ERROR_CONNECTION_PRIORITY_REQUEST = "Error on connection priority request";

		private final BlockingDeque<Request> mTaskQueue = new LinkedBlockingDeque<>();
		private Deque<Request> mInitQueue;
		private boolean mInitInProgress;
		private boolean mOperationInProgress = true; // Initially true to block operations before services are discovered.
		/**
		 * This flag is required to resume operations after the connection priority request was made.
		 * It is used only on Android Oreo and newer, as only there there is onConnectionUpdated callback.
		 * However, as this callback is triggered every time the connection parameters change, even
		 * when such request wasn't made, this flag ensures the nextRequest() method won't be called
		 * during another operation.
		 */
		private boolean mConnectionPriorityOperationInProgress = false;

		/**
		 * This method should return <code>true</code> when the gatt device supports the required services.
		 *
		 * @param gatt the gatt device with services discovered
		 * @return <code>true</code> when the device has teh required service
		 */
		protected abstract boolean isRequiredServiceSupported(final BluetoothGatt gatt);

		/**
		 * This method should return <code>true</code> when the gatt device supports the optional services.
		 * The default implementation returns <code>false</code>.
		 *
		 * @param gatt the gatt device with services discovered
		 * @return <code>true</code> when the device has teh optional service
		 */
		protected boolean isOptionalServiceSupported(final BluetoothGatt gatt) {
			return false;
		}

		/**
		 * This method should return a list of requests needed to initialize the profile.
		 * Enabling Service Change indications for bonded devices and reading the Battery Level value and enabling Battery Level notifications
		 * is handled before executing this queue. The queue should not have requests that are not available, e.g. should not
		 * read an optional service when it is not supported by the connected device.
		 * <p>This method is called when the services has been discovered and the device is supported (has required service).</p>
		 *
		 * @param gatt the gatt device with services discovered
		 * @return the queue of requests
		 */
		protected abstract Deque<Request> initGatt(final BluetoothGatt gatt);

		/**
		 * Called then the initialization queue is complete.
		 */
		protected void onDeviceReady() {
//			mCallbacks.onDeviceReady(mBluetoothGatt.getDevice());

		}

		/**
		 * This method should nullify all services and characteristics of the device.
		 * It's called when the device is no longer connected, either due to user action
		 * or a link loss.
		 */
		protected abstract void onDeviceDisconnected();

		public void notifyDeviceDisconnected(final BluetoothDevice device) {
			mConnected = false;
			mConnectionState = BluetoothGatt.STATE_DISCONNECTED;
			if (mUserDisconnected) {
				BleLOG.i(TAG, "Disconnected+++++++++++");
				mCallbacks.onDeviceDisconnected(device);
				close();
			} else {
				BleLOG.w(TAG, "Connection lost");
				mCallbacks.onLinklossOccur(device);
				// We are not closing the connection here as the device should try to reconnect automatically.
				// This may be only called when the shouldAutoConnect() method returned true.
			}
			onDeviceDisconnected();
		}

		/**
		 * Callback reporting the result of a characteristic read operation.
		 *
		 * @param gatt           GATT client
		 * @param characteristic Characteristic that was read from the associated remote device.
		 */
		protected void onCharacteristicRead(final BluetoothGatt gatt, final BluetoothGattCharacteristic characteristic) {
			// do nothing
		}

		/**
		 * Callback indicating the result of a characteristic write operation.
		 * <p>If this callback is invoked while a reliable write transaction is
		 * in progress, the value of the characteristic represents the value
		 * reported by the remote device. An application should compare this
		 * value to the desired value to be written. If the values don't match,
		 * the application must abort the reliable write transaction.
		 *
		 * @param gatt           GATT client
		 * @param characteristic Characteristic that was written to the associated remote device.
		 */
		protected void onCharacteristicWrite(final BluetoothGatt gatt, final BluetoothGattCharacteristic characteristic) {
			// do nothing
		}

		/**
		 * Callback reporting the result of a descriptor read operation.
		 *
		 * @param gatt       GATT client
		 * @param descriptor Descriptor that was read from the associated remote device.
		 */
		protected void onDescriptorRead(int status, final BluetoothGatt gatt, final BluetoothGattDescriptor descriptor) {
			// do nothing
		}

		/**
		 * Callback indicating the result of a descriptor write operation.
		 * <p>If this callback is invoked while a reliable write transaction is in progress,
		 * the value of the characteristic represents the value reported by the remote device.
		 * An application should compare this value to the desired value to be written.
		 * If the values don't match, the application must abort the reliable write transaction.
		 *
		 * @param gatt       GATT client
		 * @param descriptor Descriptor that was written to the associated remote device.
		 */
		protected void onDescriptorWrite(final BluetoothGatt gatt, final BluetoothGattDescriptor descriptor) {
			// do nothing
		}
		protected void onOneNotificationsEnabled(final BluetoothGatt gatt, final BluetoothGattDescriptor descriptor) {
			// do nothing
		}
		/**
		 * Callback reporting the value of Battery Level characteristic which could have
		 * been received by Read or Notify operations.
		 *
		 * @param gatt  GATT client
		 * @param value the battery value in percent
		 */
		protected void onBatteryValueReceived(final BluetoothGatt gatt, final int value) {
			// do nothing
		}

		/**
		 * Callback indicating a notification has been received.
		 *
		 * @param gatt           GATT client
		 * @param characteristic Characteristic from which the notification came.
		 */
		protected void onCharacteristicNotified(final BluetoothGatt gatt, final BluetoothGattCharacteristic characteristic) {
			// do nothing
		}

		/**
		 * Callback indicating an indication has been received.
		 *
		 * @param gatt           GATT client
		 * @param characteristic Characteristic from which the indication came.
		 */
		protected void onCharacteristicIndicated(final BluetoothGatt gatt, final BluetoothGattCharacteristic characteristic) {
			// do nothing
		}

		/**
		 * Method called when the MTU request has finished with success. The MTU value may
		 * be different than requested one.
		 *
		 * @param mtu the new MTU (Maximum Transfer Unit)
		 */
		protected void onMtuChanged(final int mtu) {
			// do nothing
		}
		protected void onDeviceConnected(BluetoothGatt gatt){
			// do nothing
		}
		/**
		 * Callback indicating the connection parameters were updated. Works on Android 8+.
		 *
		 * @param interval Connection interval used on this connection, 1.25ms unit. Valid range is from
		 *                 6 (7.5ms) to 3200 (4000ms).
		 * @param latency  Slave latency for the connection in number of connection events. Valid range
		 *                 is from 0 to 499
		 * @param timeout  Supervision timeout for this connection, in 10ms unit. Valid range is from 10
		 *                 (0.1s) to 3200 (32s)
		 */
		@TargetApi(Build.VERSION_CODES.O)
		protected void onConnectionUpdated(final int interval, final int latency, final int timeout) {
			// do nothing
		}

		private void onError(final BluetoothDevice device, final String message, final int errorCode) {
			BleLOG.e(TAG, "Error (0x" + Integer.toHexString(errorCode) + "): " + GattError.parse(errorCode));
			mCallbacks.onError(device, message, errorCode);
		}

		@Override
		public void onConnectionStateChange(final BluetoothGatt gatt, final int status, final int newState) {
			BleLOG.d(TAG, "[Callback] Connection state changed with status: " + status + " and new state: " + newState + " (" + stateToString(newState) + ")");

			if (status == BluetoothGatt.GATT_SUCCESS && newState == BluetoothProfile.STATE_CONNECTED) {
				// Notify the parent activity/service
				BleLOG.i(TAG, "Connected to " + gatt.getDevice().getAddress());
				mConnected = true;
				mConnectionState = BluetoothGatt.STATE_CONNECTED;
				onDeviceConnected(gatt);
				mCallbacks.onDeviceConnected(gatt.getDevice());
				/*
				 * The onConnectionStateChange event is triggered just after the Android connects to a device.
				 * In case of bonded devices, the encryption is reestablished AFTER this callback is called.
				 * Moreover, when the device has Service Changed indication enabled, and the list of services has changed (e.g. using the DFU),
				 * the indication is received few hundred milliseconds later, depending on the connection interval.
				 * When received, Android will start performing a service discovery operation on its own, internally,
				 * and will NOT notify the app that services has changed.
				 *
				 * If the gatt.discoverServices() method would be invoked here with no delay, if would return cached services,
				 * as the SC indication wouldn't be received yet.
				 * Therefore we have to postpone the service discovery operation until we are (almost, as there is no such callback) sure,
				 * that it has been handled.
				 * TODO: Please calculate the proper delay that will work in your solution.
				 * It should be greater than the time from LLCP Feature Exchange to ATT Write for Service Change indication.
				 * If your device does not use Service Change indication (for example does not have DFU) the delay may be 0.
				 */
				final boolean bonded = gatt.getDevice().getBondState() == BluetoothDevice.BOND_BONDED;
				final int delay = bonded ? 1600 : 0; // around 1600 ms is required when connection interval is ~45ms.
				if (delay > 0)
					BleLOG.d(TAG, "wait(" + delay + "ms)");
				mHandler.postDelayed(() -> {
					// Some proximity tags (e.g. nRF PROXIMITY) initialize bonding automatically when connected.
					if(bondIngDiscoverServices == true){
						BleLOG.v(TAG, "不关心是否在绑定中，Discovering Services...");
						BleLOG.d(TAG, "不关心是否在绑定中，gatt.discoverServices()");
						gatt.discoverServices();
					}else{
						//在连接过程中，如果已经在绑定中了，就不再发现服务了，因为绑定成功后，还会再次去发现服务的
						if (gatt.getDevice().getBondState() != BluetoothDevice.BOND_BONDING) {
							BleLOG.v(TAG, "Discovering Services...");
							BleLOG.d(TAG, "gatt.discoverServices()");
							gatt.discoverServices();
						}
					}

				}, delay);
			} else {
                mCallbacks.onError(gatt.getDevice(), ERROR_CONNECTION_STATE_CHANGE, status);//断开状态码
				if (newState == BluetoothProfile.STATE_DISCONNECTED) {
					if (status != BluetoothGatt.GATT_SUCCESS)
						BleLOG.w(TAG, "Error: (0x" + Integer.toHexString(status) + "): " + GattError.parseConnectionError(status));

					mOperationInProgress = true; // no more calls are possible
					mInitQueue = null;
					mTaskQueue.clear();
					final boolean wasConnected = mConnected;
					// if (mConnected) { // Checking mConnected prevents from calling onDeviceDisconnected if connection attempt failed. This check is not necessary
//					notifyDeviceDisconnected(gatt.getDevice()); // This sets the mConnected flag to false
					mHandler.sendEmptyMessageDelayed(MyHandlerCallback.handler_wait_acl_discontion,2000);
					// }
					// Try to reconnect if the initial connection was lost because of a link loss or timeout, and shouldAutoConnect() returned true during connection attempt.
					// This time it will set the autoConnect flag to true (gatt.connect() forces autoConnect true)
					if (mInitialConnection) {
						connect(gatt.getDevice());
					}

					if (wasConnected || status == BluetoothGatt.GATT_SUCCESS)
						return;
				} else {
					if (status != BluetoothGatt.GATT_SUCCESS)
						BleLOG.e(TAG, "Error (0x" + Integer.toHexString(status) + "): " + GattError.parseConnectionError(status));
				}

			}
		}

		@Override
		public void onServicesDiscovered(final BluetoothGatt gatt, final int status) {
			if (status == BluetoothGatt.GATT_SUCCESS) {
				BleLOG.i(TAG, "Services Discovered");
				if (isRequiredServiceSupported(gatt)) {
					BleLOG.v(TAG, "Primary service found");
					final boolean optionalServicesFound = isOptionalServiceSupported(gatt);
					if (optionalServicesFound)
						BleLOG.v(TAG, "Secondary service found");

					// Notify the parent activity
					mCallbacks.onServicesDiscovered(gatt.getDevice(), optionalServicesFound);

					// Obtain the queue of initialization requests
					mInitInProgress = true;
					mInitQueue = initGatt(gatt);

					// Before we start executing the initialization queue some other tasks need to be done.
					if (mInitQueue == null)
						mInitQueue = new LinkedList<>();

					// Note, that operations are added in reverse order to the front of the queue.

					// 3. Enable Battery Level notifications if required (if this char. does not exist, this operation will be skipped)
					if (mCallbacks.shouldEnableBatteryLevelNotifications(gatt.getDevice()))
						mInitQueue.addFirst(Request.newEnableBatteryLevelNotificationsRequest());
					// 2. Read Battery Level characteristic (if such does not exist, this will be skipped)
					mInitQueue.addFirst(Request.newReadBatteryLevelRequest());
					// 1. On devices running Android 4.3-6.0 the Service Changed characteristic needs to be enabled by the app (for bonded devices)
					if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N)
						mInitQueue.addFirst(Request.newEnableServiceChangedIndicationsRequest());

//					mOperationInProgress = false;
					nextRequest(false);
				} else {
					BleLOG.w(TAG, "Device is not supported");
					mCallbacks.onDeviceNotSupported(gatt.getDevice());
					disconnect();
				}
			} else {
				Log.e(TAG, "onServicesDiscovered error " + status);
				onError(gatt.getDevice(), ERROR_DISCOVERY_SERVICE, status);
			}
		}

		@Override
		public void onCharacteristicRead(final BluetoothGatt gatt, final BluetoothGattCharacteristic characteristic, final int status) {
			if (status == BluetoothGatt.GATT_SUCCESS) {
				BleLOG.d(TAG, "Read Response received from "
						+ GattServiceCharacteristic.findNameByUUid(characteristic.getUuid().toString())
						+ ", value: " + ParserUtils.parse(characteristic));

				if (isBatteryLevelCharacteristic(characteristic)) {
					final int batteryValue = characteristic.getIntValue(BluetoothGattCharacteristic.FORMAT_UINT8, 0);
					BleLOG.a(TAG, "Battery level received: " + batteryValue + "%");
					mBatteryValue = batteryValue;
					onBatteryValueReceived(gatt, batteryValue);
					mCallbacks.onBatteryValueReceived(gatt.getDevice(), batteryValue);
				} else {
					// The value has been read. Notify the manager and proceed with the initialization queue.
					onCharacteristicRead(gatt, characteristic);
				}
			} else if (status == BluetoothGatt.GATT_INSUFFICIENT_AUTHENTICATION) {
				if (gatt.getDevice().getBondState() != BluetoothDevice.BOND_NONE) {
					// This should never happen but it used to: http://stackoverflow.com/a/20093695/2115352
					Log.w(TAG, ERROR_AUTH_ERROR_WHILE_BONDED);
					mCallbacks.onError(gatt.getDevice(), ERROR_AUTH_ERROR_WHILE_BONDED, status);
				}
			} else {
				Log.e(TAG, "onCharacteristicRead error " + status);
				onError(gatt.getDevice(), ERROR_READ_CHARACTERISTIC, status);
			}
//			mOperationInProgress = false;
			nextRequest(false);
		}

		@Override
		public void onCharacteristicWrite(final BluetoothGatt gatt, final BluetoothGattCharacteristic characteristic, final int status) {
			if (status == BluetoothGatt.GATT_SUCCESS) {
				BleLOG.d(TAG, "Data written to "
						+ GattServiceCharacteristic.findNameByUUid(characteristic.getUuid().toString())
						+ ", value: " + ParserUtils.parse(characteristic));
				// The value has been written. Notify the manager and proceed with the initialization queue.
				onCharacteristicWrite(gatt, characteristic);
			} else if (status == BluetoothGatt.GATT_INSUFFICIENT_AUTHENTICATION) {
				if (gatt.getDevice().getBondState() != BluetoothDevice.BOND_NONE) {
					// This should never happen but it used to: http://stackoverflow.com/a/20093695/2115352
					Log.w(TAG, ERROR_AUTH_ERROR_WHILE_BONDED);
					mCallbacks.onError(gatt.getDevice(), ERROR_AUTH_ERROR_WHILE_BONDED, status);
				}
			} else {
				Log.e(TAG, "onCharacteristicWrite error " + status);
				onError(gatt.getDevice(), ERROR_WRITE_CHARACTERISTIC, status);
			}
//			mOperationInProgress = false;
			nextRequest(false);


		}
		@Override
		public void onDescriptorRead(final BluetoothGatt gatt, final BluetoothGattDescriptor descriptor, final int status) {
			// The value has been read. Notify the manager and proceed with the initialization queue.
			onDescriptorRead(status,gatt, descriptor);
			if (status == BluetoothGatt.GATT_SUCCESS) {
				BleLOG.d(TAG, "Read Response received from descr. "
						+ GattServiceCharacteristic.findNameByUUid(descriptor.getUuid().toString())
						+ ", value: " + ParserUtils.parse(descriptor));

			} else if (status == BluetoothGatt.GATT_INSUFFICIENT_AUTHENTICATION) {
				if (gatt.getDevice().getBondState() != BluetoothDevice.BOND_NONE) {
					// This should never happen but it used to: http://stackoverflow.com/a/20093695/2115352
					Log.w(TAG, ERROR_AUTH_ERROR_WHILE_BONDED);
					mCallbacks.onError(gatt.getDevice(), ERROR_AUTH_ERROR_WHILE_BONDED, status);
				}
			} else {
				Log.e(TAG, "onDescriptorRead error " + status);
				onError(gatt.getDevice(), ERROR_READ_DESCRIPTOR, status);
			}
//			mOperationInProgress = false;
			nextRequest(false);
		}

		@Override
		public void onDescriptorWrite(final BluetoothGatt gatt, final BluetoothGattDescriptor descriptor, final int status) {
			if (status == BluetoothGatt.GATT_SUCCESS) {
				BleLOG.i(TAG, "Data written to descr. "
						+ GattServiceCharacteristic.findNameByUUid(descriptor.getUuid().toString())
						+ ", value: " + ParserUtils.parse(descriptor));

				if (isServiceChangedCCCD(descriptor)) {
					BleLOG.a(TAG, "Service Changed notifications enabled");
				} else if (isBatteryLevelCCCD(descriptor)) {
					final byte[] value = descriptor.getValue();
					if (value != null && value.length == 2 && value[1] == 0x00) {
						if (value[0] == 0x01) {
							BleLOG.a(TAG, "Battery Level notifications enabled");
						} else {
							BleLOG.a(TAG, "Battery Level notifications disabled");
						}
					} else {
						onDescriptorWrite(gatt, descriptor);
					}
				} else if (isCCCD(descriptor)) {
					final byte[] value = descriptor.getValue();
					if (value != null && value.length == 2 && value[1] == 0x00) {
						switch (value[0]) {
							case 0x00:
								BleLOG.a(TAG, "Notifications and indications disabled");
								break;
							case 0x01:
								onOneNotificationsEnabled(gatt, descriptor);
								BleLOG.a(TAG, "Notifications enabled");
								break;
							case 0x02:
								onOneNotificationsEnabled(gatt, descriptor);
								BleLOG.a(TAG, "Indications enabled");
								break;
						}
					} else {
						onDescriptorWrite(gatt, descriptor);
					}
				} else {
					onDescriptorWrite(gatt, descriptor);
				}
			} else if (status == BluetoothGatt.GATT_INSUFFICIENT_AUTHENTICATION) {
				if (gatt.getDevice().getBondState() != BluetoothDevice.BOND_NONE) {
					// This should never happen but it used to: http://stackoverflow.com/a/20093695/2115352
					Log.w(TAG, ERROR_AUTH_ERROR_WHILE_BONDED);
					mCallbacks.onError(gatt.getDevice(), ERROR_AUTH_ERROR_WHILE_BONDED, status);
				}
			} else {
				Log.e(TAG, "onDescriptorWrite error " + status);
				onError(gatt.getDevice(), ERROR_WRITE_DESCRIPTOR, status);
			}
//			mOperationInProgress = false;
			nextRequest(false);
		}

		@Override
		public void onCharacteristicChanged(final BluetoothGatt gatt, final BluetoothGattCharacteristic characteristic) {
			final String data = ParserUtils.parse(characteristic);

			if (isBatteryLevelCharacteristic(characteristic)) {
				BleLOG.w(TAG, "Notification received from "
						+ GattServiceCharacteristic.findNameByUUid(characteristic.getUuid().toString())
						+ ", value: " + data);
				final int batteryValue = characteristic.getIntValue(BluetoothGattCharacteristic.FORMAT_UINT8, 0);
				BleLOG.a(TAG, "Battery level received: " + batteryValue + "%");
				mBatteryValue = batteryValue;
				onBatteryValueReceived(gatt, batteryValue);
				mCallbacks.onBatteryValueReceived(gatt.getDevice(), batteryValue);
			} else {
				final BluetoothGattDescriptor cccd = characteristic.getDescriptor(CLIENT_CHARACTERISTIC_CONFIG_DESCRIPTOR_UUID);
				final boolean notifications = cccd == null || cccd.getValue() == null || cccd.getValue().length != 2 || cccd.getValue()[0] == 0x01;

				if (notifications) {
					BleLOG.d(TAG, "Notification received from "
							+ GattServiceCharacteristic.findNameByUUid(characteristic.getUuid().toString())
							+ ", value: " + data);
					onCharacteristicNotified(gatt, characteristic);
				} else { // indications
					BleLOG.d(TAG, "Indication received from "
							+ GattServiceCharacteristic.findNameByUUid(characteristic.getUuid().toString())
							+ ", value: " + data);
					onCharacteristicIndicated(gatt, characteristic);
				}
			}
		}

		@Override
		public void onMtuChanged(final BluetoothGatt gatt, final int mtu, final int status) {
			BleLOG.d(TAG, "MTU changed to: " + mtu);
			if (status == BluetoothGatt.GATT_SUCCESS) {
				onMtuChanged(mtu);
			} else {
				Log.e(TAG, "onMtuChanged error: " + status + ", mtu: " + mtu);
				onError(gatt.getDevice(), ERROR_MTU_REQUEST, status);
			}
//			mOperationInProgress = false;
			nextRequest(false);
		}

//		 @Override

		/**
		 * Callback indicating the connection parameters were updated. Works on Android 8+.
		 *
		 * @param gatt     GATT client involved
		 * @param interval Connection interval used on this connection, 1.25ms unit. Valid range is from
		 *                 6 (7.5ms) to 3200 (4000ms).
		 * @param latency  Slave latency for the connection in number of connection events. Valid range
		 *                 is from 0 to 499
		 * @param timeout  Supervision timeout for this connection, in 10ms unit. Valid range is from 10
		 *                 (0.1s) to 3200 (32s)
		 * @param status   {@link BluetoothGatt#GATT_SUCCESS} if the connection has been updated
		 *                 successfully
		 */
		public void onConnectionUpdated(final BluetoothGatt gatt, final int interval, final int latency, final int timeout, final int status) {
			if (status == BluetoothGatt.GATT_SUCCESS) {
				BleLOG.i(TAG, "Connection parameters updated (interval: " + (interval * 1.25) + "ms, latency: " + latency + ", timeout: " + (timeout * 10) + "ms)");
				onConnectionUpdated(interval, latency, timeout);
			} else if (status == 0x3b) { // HCI_ERR_UNACCEPT_CONN_INTERVAL
				Log.e(TAG, "onConnectionUpdated received status: Unacceptable connection interval, interval: " + interval + ", latency: " + latency + ", timeout: " + timeout);
				BleLOG.w(TAG, "Connection parameters update failed with status: UNACCEPT CONN INTERVAL (0x3b) (interval: " + (interval * 1.25) + "ms, latency: " + latency + ", timeout: " + (timeout * 10) + "ms)");
			} else {
				Log.e(TAG, "onConnectionUpdated received status: " + status + ", interval: " + interval + ", latency: " + latency + ", timeout: " + timeout);
				BleLOG.w(TAG, "Connection parameters update failed with status " + status + " (interval: " + (interval * 1.25) + "ms, latency: " + latency + ", timeout: " + (timeout * 10) + "ms)");
				mCallbacks.onError(gatt.getDevice(), ERROR_CONNECTION_PRIORITY_REQUEST, status);
			}
			if (mConnectionPriorityOperationInProgress) {
				mConnectionPriorityOperationInProgress = false;
//				mOperationInProgress = false;
				nextRequest(false);
			}
		}

		/**
		 * Executes the next request. If the last element from the initialization queue has been executed
		 * the {@link #onDeviceReady()} callback is called.
		 */
		private BleEnum nextRequest(boolean isMainThreed) {
			if (mOperationInProgress && isMainThreed)
				return BleEnum.nextRequest_no_Progress;

			// Get the first request from the init queue
			Request request = mInitQueue != null ? mInitQueue.poll() : null;

			// Are we done with initializing?
			if (request == null) {
				if (mInitInProgress) {
					mInitQueue = null; // release the queue
					mInitInProgress = false;
					onDeviceReady();
				}
				// If so, we can continue with the task queue
				try {
					request = mTaskQueue.poll();
				}catch (Exception e){
					e.printStackTrace();
				}
				if (request == null) {
					mCurrenRequset = null;
					mOperationInProgress = false;
					// Nothing to be done for now
					return BleEnum.nextRequest_is_null;
				}
			}
			mOperationInProgress = true;
			boolean result = false;
			switch (request.type) {
				case CREATE_BOND: {
					result = internalCreateBond();
					break;
				}
				case READ: {
					mCurrenRequset = request;
					result = internalReadCharacteristic(request.characteristic);
					break;
				}
				case WRITE: {
					mCurrenRequset = request;
					final BluetoothGattCharacteristic characteristic = request.characteristic;
					characteristic.setValue(request.data);
					characteristic.setWriteType(request.writeType);
					result = internalWriteCharacteristic(characteristic);
					break;
				}
				case READ_DESCRIPTOR: {
					result = internalReadDescriptor(request.descriptor);
					break;
				}
				case WRITE_DESCRIPTOR: {
					final BluetoothGattDescriptor descriptor = request.descriptor;
					descriptor.setValue(request.data);
					result = internalWriteDescriptor(descriptor);
					break;
				}
				case ENABLE_NOTIFICATIONS: {
					result = internalEnableNotifications(request.characteristic);
					break;
				}
				case DISABLE_NOTIFICATIONS: {
					result = internalDisableNotifications(request.characteristic);
					break;
				}
				case ENABLE_INDICATIONS: {
					result = internalEnableIndications(request.characteristic);
					break;
				}
				case READ_BATTERY_LEVEL: {
					result = internalReadBatteryLevel();
					break;
				}
				case ENABLE_BATTERY_LEVEL_NOTIFICATIONS: {
					result = internalSetBatteryNotifications(true);
					break;
				}
				case DISABLE_BATTERY_LEVEL_NOTIFICATIONS: {
					result = internalSetBatteryNotifications(false);
					break;
				}
				case ENABLE_SERVICE_CHANGED_INDICATIONS: {
					result = ensureServiceChangedEnabled();
					break;
				}
				case REQUEST_MTU: {
					if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
						result = internalRequestMtu(request.value);
					}
					break;
				}
				case REQUEST_CONNECTION_PRIORITY: {
					if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
						mConnectionPriorityOperationInProgress = true;
						result = internalRequestConnectionPriority(request.value);
					} else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
						result = internalRequestConnectionPriority(request.value);
						// There is no callback for requestConnectionPriority(...) before Android Oreo.\
						// Let's give it some time to finish as the request is an asynchronous operation.
						if (result) {
							mHandler.postDelayed(() -> {
//								mOperationInProgress = false;
								nextRequest(false);
							}, 100);
						}
					}
					break;
				}
			}
			// The result may be false if given characteristic or descriptor were not found on the device,
			// or the feature is not supported on the Android.
			// In that case, proceed with next operation and ignore the one that failed.
			if (!result) {
				mConnectionPriorityOperationInProgress = false;
//				mOperationInProgress = false;
				nextRequest(false);
			}
			return BleEnum.nextRequest_next;
		}

		/**
		 * Returns true if this descriptor is from the Service Changed characteristic.
		 *
		 * @param descriptor the descriptor to be checked
		 * @return true if the descriptor belongs to the Service Changed characteristic
		 */
		private boolean isServiceChangedCCCD(final BluetoothGattDescriptor descriptor) {
			if (descriptor == null)
				return false;

			return SERVICE_CHANGED_CHARACTERISTIC.equals(descriptor.getCharacteristic().getUuid());
		}

		/**
		 * Returns true if the characteristic is the Battery Level characteristic.
		 *
		 * @param characteristic the characteristic to be checked
		 * @return true if the characteristic is the Battery Level characteristic.
		 */
		private boolean isBatteryLevelCharacteristic(final BluetoothGattCharacteristic characteristic) {
			if (characteristic == null)
				return false;

			return BATTERY_LEVEL_CHARACTERISTIC.equals(characteristic.getUuid());
		}

		/**
		 * Returns true if this descriptor is from the Battery Level characteristic.
		 *
		 * @param descriptor the descriptor to be checked
		 * @return true if the descriptor belongs to the Battery Level characteristic
		 */
		private boolean isBatteryLevelCCCD(final BluetoothGattDescriptor descriptor) {
			if (descriptor == null)
				return false;

			return BATTERY_LEVEL_CHARACTERISTIC.equals(descriptor.getCharacteristic().getUuid());
		}

		/**
		 * Returns true if this descriptor is a Client Characteristic Configuration descriptor (CCCD).
		 *
		 * @param descriptor the descriptor to be checked
		 * @return true if the descriptor is a CCCD
		 */
		private boolean isCCCD(final BluetoothGattDescriptor descriptor) {
			if (descriptor == null)
				return false;

			return CLIENT_CHARACTERISTIC_CONFIG_DESCRIPTOR_UUID.equals(descriptor.getUuid());
		}
	}

	private static final int PAIRING_VARIANT_PIN = 0;
	private static final int PAIRING_VARIANT_PASSKEY = 1;
	private static final int PAIRING_VARIANT_PASSKEY_CONFIRMATION = 2;
	private static final int PAIRING_VARIANT_CONSENT = 3;
	private static final int PAIRING_VARIANT_DISPLAY_PASSKEY = 4;
	private static final int PAIRING_VARIANT_DISPLAY_PIN = 5;
	private static final int PAIRING_VARIANT_OOB_CONSENT = 6;

	protected String pairingVariantToString(final int variant) {
		switch (variant) {
			case PAIRING_VARIANT_PIN:
				return "PAIRING_VARIANT_PIN";
			case PAIRING_VARIANT_PASSKEY:
				return "PAIRING_VARIANT_PASSKEY";
			case PAIRING_VARIANT_PASSKEY_CONFIRMATION:
				return "PAIRING_VARIANT_PASSKEY_CONFIRMATION";
			case PAIRING_VARIANT_CONSENT:
				return "PAIRING_VARIANT_CONSENT";
			case PAIRING_VARIANT_DISPLAY_PASSKEY:
				return "PAIRING_VARIANT_DISPLAY_PASSKEY";
			case PAIRING_VARIANT_DISPLAY_PIN:
				return "PAIRING_VARIANT_DISPLAY_PIN";
			case PAIRING_VARIANT_OOB_CONSENT:
				return "PAIRING_VARIANT_OOB_CONSENT";
			default:
				return "UNKNOWN";
		}
	}

	protected String bondStateToString(final int state) {
		switch (state) {
			case BluetoothDevice.BOND_NONE:
				return "BOND_NONE";
			case BluetoothDevice.BOND_BONDING:
				return "BOND_BONDING";
			case BluetoothDevice.BOND_BONDED:
				return "BOND_BONDED";
			default:
				return "UNKNOWN";
		}
	}

	protected String getWriteType(final int type) {
		switch (type) {
			case BluetoothGattCharacteristic.WRITE_TYPE_DEFAULT:
				return "WRITE REQUEST";
			case BluetoothGattCharacteristic.WRITE_TYPE_NO_RESPONSE:
				return "WRITE COMMAND";
			case BluetoothGattCharacteristic.WRITE_TYPE_SIGNED:
				return "WRITE SIGNED";
			default:
				return "UNKNOWN: " + type;
		}
	}

	/**
	 * Converts the connection state to String value
	 *
	 * @param state the connection state
	 * @return state as String
	 */
	protected String stateToString(final int state) {
		switch (state) {
			case BluetoothProfile.STATE_CONNECTED:
				return "CONNECTED";
			case BluetoothProfile.STATE_CONNECTING:
				return "CONNECTING";
			case BluetoothProfile.STATE_DISCONNECTING:
				return "DISCONNECTING";
			default:
				return "DISCONNECTED";
		}
	}
	protected class MyHandlerCallback implements Handler.Callback{
		public static final int handler_discoverServices_time_out = 10001;
		public static final int handler_enable_notify_time_out = 10002;
		/**等待ACL的断开*/
		public static final int handler_wait_acl_discontion = 10003;
		@Override
		public boolean handleMessage(Message msg) {
			switch (msg.what){
				case handler_discoverServices_time_out:{
					BleLOG.e(TAG,"discoverServices time out");
					BluetoothGatt gatt = mBluetoothGatt;
					if(mCallbacks != null) {
						BluetoothDevice device = (BluetoothDevice)msg.obj;
						mCallbacks.onError(device, "discoverServices time out", 4001);
					}
					disconnect();
				}
				break;
				case handler_enable_notify_time_out:{
					BleLOG.e(TAG,"enable notify time out");
					if(msg.obj != null){
						HashMap<String, BluetoothGattCharacteristic> map= (HashMap<String, BluetoothGattCharacteristic>) msg.obj;
						if(map != null && !map.isEmpty()){
							Set<String> keys = map.keySet();
							for (String key:keys ) {
								BluetoothGattCharacteristic bgc = map.get(key);
								if(bgc != null) {
									BleLOG.e(TAG, "还没使能的有： " + key + "------" + (bgc.getUuid().toString()));
								}
							}
						}
					}
					BluetoothGatt gatt = mBluetoothGatt;
					if(mCallbacks != null && gatt != null)
						mCallbacks.onError(gatt.getDevice(),"enable notify time out",4002);
					disconnect();
				}
				break;
				case handler_wait_acl_discontion:
					mHandler.removeMessages(handler_wait_acl_discontion);
					if(mGattCallback != null)
						mGattCallback.notifyDeviceDisconnected(mBluetoothDevice);
					break;
			}
			return false;
		}
	}

	/**
	 * 设置在绑定中时，是否去发现服务
	 * @param bondIngDiscoverServices
	 */
	public void setBondIngDiscoverServices(boolean bondIngDiscoverServices) {
		this.bondIngDiscoverServices = bondIngDiscoverServices;
	}
}
